package com.distributed.client;

import RECORDMANAGER.ReturnData;

import java.io.*;
import java.net.Socket;
import java.util.Collection;
import java.util.Scanner;

import com.github.mnadeem.TableNameParser;

public class Client {

    private static final String server_address = "localhost";
    private static final int master_port = 8000;
    private static final int region_server_port = 8001;
    private MatserSocketManager master_socket_manager;
    private RegionSocketManager region_socket_manager;
    private Scanner input;

    public Client() {
        input = new Scanner(System.in);
    }

    public void start() {
        greet();
        while (true) {
            try {
                connectToMatser();
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
            greetSql();
            String input = getInput();
            Collection<String> table_names = SQLParser.parseTableName(input);
            String table_name = table_names.iterator().next();
            if(input.startsWith("create") || input.startsWith("CREATE")) {
                table_name = "create "+table_name;
            }
            System.out.printf("send to MASTER: %s\n", table_name);
            String region_address = null;
            try {
                region_address = master_socket_manager.sendMessageAndWaitReply(table_name);
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
            if(region_address.length() < 1) {
                System.out.println("Empty return from MASTER");
                continue;
            }
            if (region_address.charAt(0) < '0' || region_address.charAt(0) > '9') {
                // ERROR!
                System.out.println("No available region server.");
                continue;
            }
            System.out.printf("Send to master successfully.\nTry to reach region: %s\n", region_address);
            try {
                connectToRegion(region_address, region_server_port);
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
            System.out.printf("You have connected to REGION server: %s \n", region_address);
            ReturnData return_data = region_socket_manager.sendMessageAndWaitReply(input);
            showResult(return_data);
            if (input.contains("quit")) {
                try {
                    disconnect();
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                }
                break;
            }
        }

    }

    public void greet() {
        System.out.printf("Client start.\nYou have connected to master server %s \n", server_address);
    }

    public void greetSql() {
        System.out.println("Please enter your SQL statements:");
    }

    public String getInput() {
        String line = "";
        StringBuilder sql = new StringBuilder();

        while (line.isEmpty() || line.charAt(line.length() - 1) != ';') {
            line = input.nextLine();
            if (!line.isEmpty()) {
                sql.append(line);
                sql.append(' ');
            }
        }

        return sql.toString().trim();
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }

    public void connectToMatser() throws IOException {
        master_socket_manager = new MatserSocketManager(server_address, master_port);
    }

    public void connectToRegion(String address, int port) throws IOException {
        region_socket_manager = new RegionSocketManager(address, port);
    }

    public void showResult(ReturnData data) {
        int row_num = data.returnData.size();
        if (row_num > 0) {
            // header
            System.out.print("---------------------------------------------------------\n");
            for (int j = 0; j < data.returnData.get(row_num - 1).get_attribute_size(); j++) {
                System.out.print("| " + data.returnData.get(row_num - 1).get_attribute_value(j) + " ");
            }
            System.out.println("|");
            // each rows
            for (int i = 0; i < row_num - 1; i++) {
                System.out.print("---------------------------------------------------------\n");
                for (int j = 0; j < data.returnData.get(i).get_attribute_size(); j++) {
                    System.out.print("| " + data.returnData.get(i).get_attribute_value(j) + " ");
                }
                System.out.println("|");
            }
            // end row
            System.out.print("---------------------------------------------------------\n");
        }
        System.out.println(data.info);
    }

    public void disconnect() throws IOException {
        master_socket_manager.close();
        if (region_socket_manager != null) {
            region_socket_manager.close();
        }
    }
}

class MatserSocketManager {
    private Socket socket;
    private OutputStream output_stream;
    private DataOutputStream data_output_stream;
    private InputStream input_stream;
    private DataInputStream data_input_stream;

    public MatserSocketManager(String address, int port) throws IOException {
        socket = new Socket(address, port);
        output_stream = socket.getOutputStream();
        data_output_stream = new DataOutputStream(output_stream);
        input_stream = socket.getInputStream();
        data_input_stream = new DataInputStream(input_stream);
    }

    public String sendMessageAndWaitReply(String message) throws IOException {
        this.sendMessage(message);
        return data_input_stream.readUTF();
    }

    public void sendMessage(String message) throws IOException {
        data_output_stream.writeUTF(message);
    }

    public void close() throws IOException {
        output_stream.close();
        data_output_stream.close();
        input_stream.close();
        data_input_stream.close();
        socket.close();
    }
}

class RegionSocketManager {
    private Socket socket;
    private OutputStream output_stream;
    private PrintWriter print_writer;
    private ObjectInputStream object_input_stream;

    RegionSocketManager(String address, int port) throws IOException {
        socket = new Socket(address, port);
        output_stream = socket.getOutputStream();
        print_writer = new PrintWriter(output_stream);
        object_input_stream = new ObjectInputStream(socket.getInputStream());
    }

    public ReturnData sendMessageAndWaitReply(String message) {
        String m = message.substring(0, message.length() - 1);
        print_writer.println(m);
        print_writer.flush();
        ReturnData return_data = null;
        try {
            return_data = (ReturnData) object_input_stream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return new ReturnData(false, "Fail to receive from Region.");
        }
        return return_data;
    }

    public void close() throws IOException {
        output_stream.close();
        print_writer.close();
        object_input_stream.close();
        socket.close();
    }
}

class SQLParser {
    SQLParser() {

    }

    public static Collection<String> parseTableName(String sql) {
        return new TableNameParser(sql).tables();
    }
}